# -*- coding: utf-8 -*-

import os,logging,re
from .. import config as C
from .. import utils
from ..utils import fs
from ..utils import py
from ..build.preprocess.name_field import NameField
from ..build.caches.file_cache import FileCache

DECLARATION_FILE = '''
// generate by lcc-framework

declare module lcc$TABLES {

%s

}

'''

class TablePublisher:
	'''
		数据表发布器
	'''
	def __init__(self):
		self._config = utils.getConfig()
		self._publishForce = py.getDictPath(self._config, 'publish.force', False)
		self._publishTarget = py.getDictPath(self._config, 'publish.target', [])
		self._tmempClientAssetPath = os.path.join(C.BUILD_PUBLISH_PATH, 'tables-client')
		if not os.path.exists(self._tmempClientAssetPath):
			os.makedirs(self._tmempClientAssetPath)
		self._tmempServerAssetPath = os.path.join(C.BUILD_PUBLISH_PATH, 'tables-server')
		if not os.path.exists(self._tmempServerAssetPath):
			os.makedirs(self._tmempServerAssetPath)

	def collectFiles(self, buildPackPath):
		'''
			收集表文件
		'''
		files = fs.globFiles([ r'**/*' ], os.path.join(buildPackPath, 'postprocess', 'tables'))
		files = [ os.path.normpath(f) for f in files if os.path.isfile(f)]
		return files

	def checkUpdate(self):
		'''
			检测更新
		'''
		updates = set()
		for pack in os.listdir(C.BUILD_CACHES_PATH):
			fileCache = FileCache(os.path.join(C.BUILD_CACHES_PATH, pack, 'publish-tables-cache.json'))
			fileCache.load()
			files = self.collectFiles(os.path.join(C.BUILD_CACHES_PATH, pack))
			if fileCache.checkFileChange(files):
				updates.add(pack)
		return updates

	def fliterTable(self,tableData, target):
		'''
			过滤表
		'''
		# 移除声明健
		for k in [ x for x in tableData['cache'].keys()]:
			if k in [ '#declareMapKey' ]:
				del tableData['cache'][k]

		# 移除非目标字段
		rmKeys = []
		rmIndexs = []
		for i in range(0, len(tableData['sheetHead'])):
			headData = tableData['sheetHead'][i]
			head = NameField(headData)
			field = head.getNote("field", head.getName())
			if target not in head.getNotes('target', [ target ]):
				rmKeys.append(field)
				rmIndexs.append(i)
		rmIndexs.reverse()
		
		if len(rmIndexs) > 0:
			tc = tableData['tableConfig']
			if tc['key'] in rmKeys or tc['value'] in rmKeys or ('listSort' in tc and tc['listSort'] in rmKeys):
				return False
			if tc['valueType'] == 'Object':
				if tc['keyType'] == 'Map':
					objects = tableData['cache'][tc['tableName']].values()
				else: # List
					objects = tableData['cache'][tc['tableName']]
				for o in objects:
					for rmkey in rmKeys:
						py.removeDictPath(o, rmkey)
			elif tc['valueType'] == 'Array':
				if tc['keyType'] == 'Map':
					arrays = tableData['cache'][tc['tableName']].values()
				else: # List
					arrays = tableData['cache'][tc['tableName']]
				for array in arrays:
					for rmi in rmIndexs:
						if rmi < len(array):
							del array[rmi]

		return True

	def publishClient(self,packPath):
		'''
			发布客户端
		'''
		pack = os.path.basename(packPath)
		clientPath = utils.fullConfigPath(self._config['path']['client'])
		tablePath = os.path.join(packPath, 'postprocess', 'tables')
		if os.path.exists(tablePath) and os.path.exists(clientPath):
			clientPackPath = utils.getClientPackPath(os.path.join(clientPath,'assets'), pack)
			if clientPackPath != None:
				clientAssetPath = clientPackPath + "/lcc-assets/tables/"
				if not os.path.exists(clientAssetPath):
					os.makedirs(clientAssetPath)
				fs.filterFiles(clientAssetPath, reserveReg=re.compile(r'.*[.]meta'), deleteReg=re.compile(r'.*'))
				assetIndexFile = os.path.normpath(clientAssetPath + "/indexes.json")
				indexConfig = {}
				langIndex = {}

			for f in os.listdir(tablePath):
				t = os.path.splitext(f)[0]
				tableFile = os.path.join(tablePath, f)
				tableData = py.readJson(tableFile)
				if not self.fliterTable(tableData, 'client'):
					logging.info('client skip table `%s`' % tableFile)
					continue
				py.writeJson(tableData['cache'], os.path.join(self._tmempClientAssetPath, '%s-%s.json' % (t, pack)), self._config.get('debug',False))
				if clientPackPath != None:
					destFile = os.path.normpath(os.path.join(clientAssetPath, '%s.json' % t))
					py.writeJson(tableData['cache'], destFile, self._config.get('debug',False))
					logging.info('generate client table `%s`' % destFile)
					langIndex[t] = [ 'asset://%s/lcc-assets/tables/%s' % (pack, t) ]
					indexConfig['table'] = langIndex

			if clientPackPath != None:
				py.writeJson(indexConfig, assetIndexFile, self._config.get('debug',False))
				
				# 更新根索引文件
				rootIndexFile = os.path.join(clientPackPath, 'index-files.json')
				if os.path.exists(rootIndexFile):
					rootIndex = py.readJson(rootIndexFile)
				else:
					rootIndex = {}
				rootIndex["table_auto"] = "asset://%s/lcc-assets/tables/indexes" % pack
				py.writeJson(rootIndex, rootIndexFile, self._config.get('debug',False))
				
		return True
		
	def publishServerTemp(self,packPath):
		'''
			发布服务器-临时
		'''
		pack = os.path.basename(packPath)
		serverPath = utils.fullConfigPath(self._config['path']['server'])
		tablePath = os.path.join(packPath, 'postprocess', 'tables')
		if os.path.exists(tablePath) and os.path.exists(serverPath):
			for f in os.listdir(tablePath):
				t = os.path.splitext(f)[0]
				tableFile = os.path.join(tablePath, f)
				tableData = py.readJson(tableFile)
				if not self.fliterTable(tableData, 'server'):
					logging.info('server skip table `%s`' % tableFile)
					continue
				destFile = os.path.normpath(os.path.join(self._tmempServerAssetPath, '%s-%s.json' % (t, pack)))
				py.writeJson(tableData['cache'], destFile, self._config.get('debug',False))
				logging.info('generate server table `%s`' % destFile)
			
		return True

	def publishServer(self):
		'''
			发布服务器
		'''
		serverPath = utils.fullConfigPath(self._config['path']['server'])
		if os.path.exists(serverPath):
			tablesMap = {}
			serverAssetPath =  serverPath + "/assets/lcc-assets/tables/"
			if not os.path.exists(serverAssetPath):
				os.makedirs(serverAssetPath)
			fs.clearDirectory(serverAssetPath)
			for f in os.listdir(self._tmempServerAssetPath):
				fname,_ = os.path.splitext(f)
				tbname, pack = fname.split('-')
				table =  py.readJson(os.path.join(self._tmempServerAssetPath, f))
				oldTable =  tablesMap.get(tbname,{})
				py.mergeDict(oldTable, table)
				tablesMap[tbname] = oldTable
			for tbname, table in tablesMap.items():
				# 处理排序
				sortMap = table.get('#listSort', None)
				if sortMap != None:
					sortInfo = sortMap.get(tbname, None)
					realTable = table.get(tbname, None)
					if realTable != None and sortInfo != None:
						sortKey = sortInfo.get('key',None)
						sortType = sortInfo.get('type', False)
						if sortKey != None:
							realTable.sort(key=lambda x:py.getDictPath(x,sortKey) if isinstance(sortKey,str) else x[sortKey], reverse=not sortType=='up')
						else:
							realTable.sort(key=lambda x:x, reverse=not sortType=='up')
					del table['#listSort']
				py.writeJson(table, os.path.join(serverAssetPath, '%s.json' % tbname), self._config.get('debug',False))

	def buildDeclaration(self, tablePath):
		'''
			构建表声明
		'''
		def buildList(array,l):
			'''
				构建列表声明
			'''
			types = []
			for i in array:
				if isinstance(i, (list, tuple)):
					types.append(buildList(i, l + 1))
				elif isinstance(i, dict):
					types.append(buildDict(i, l + 1))
				elif isinstance(i, str):
					types.append('string')
				elif isinstance(i, (int, float)):
					types.append('number')
				else:
					raise Exception('unsupport type')
			if len(set(types)) == 1:
				return '%s[]' % types[0]
			else:
				return ('[\n' + '\t' * l + '%s\n' + '\t' * (l - 1) + ']') % ((',\n' + '\t' * l).join(types))
		def buildDict(map,l):
			'''
				构建字典声明
			'''
			types = []
			for (k,i) in map.items():
				if isinstance(i, (list, tuple)):
					types.append('%s:%s' %(k, buildList(i, l + 1)))
				elif isinstance(i, dict):
					types.append('%s:%s' %(k, buildDict(i, l + 1)))
				elif isinstance(i, str):
					types.append('%s:%s' %(k, 'string'))
				elif isinstance(i, (int, float)):
					types.append('%s:%s' %(k, 'number'))
				else:
					raise Exception('unsupport type')
			return ('{\n' + '\t' * l + '%s\n' + '\t' * (l - 1) + '}') % ((',\n' + '\t' * l).join(types))
		
		# 加载所有表
		tables = {}
		if os.path.isdir(tablePath):
			for tbfile in os.listdir(tablePath):
				table = py.readJson(os.path.join(tablePath, tbfile))
				py.mergeDict(tables, table)

		# 统计类型和子类型
		subTypes = {}
		types = []
		def getSubType(stype):
			if stype in subTypes:
				stn = subTypes[stype]
			else:
				stn = "TempType_" + str(len(subTypes))
				subTypes[stype] = stn
			return stn
		for (name, table) in tables.items():
			if name.startswith('#'):
				continue
			if isinstance(table, list):
				_types = []
				for v in table:
					if isinstance(v, (list, tuple)):
						_types.append(getSubType(buildList(v, 1)))
					elif isinstance(v, dict):
						_types.append(getSubType(buildDict(v, 1)))
					elif isinstance(v, str):
						_types.append('string')
					elif isinstance(v, (int, float)):
						_types.append('number')
					else:
						raise Exception('unsupport type')
				if len(set(_types)) == 1:
					types.append('export type %s = %s[];' % (name, _types[0]))
				else:
					types.append('export type %s = %s;' % (name,'[\n\t%s\n]' % ',\n\t'.join(_types)))
			elif isinstance(table, dict):
				declare = tables.get('#declareMapKey', None)
				if declare != None:
					declareMapKey = declare.get(name, False)
				else:
					declareMapKey = False
				if declareMapKey:
					_types = []
					for (k,v) in table.items():
						if isinstance(v, (list, tuple)):
							_types.append('%s:%s' % (k, getSubType(buildList(v, 1))))
						elif isinstance(v, dict):
							_types.append('%s:%s' % (k, getSubType(buildDict(v, 1))))
						elif isinstance(v, str):
							_types.append('%s:%s' %(k, 'string'))
						elif isinstance(v, (int, float)):
							_types.append('%s:%s' %(k, 'number'))
						else:
							raise Exception('unsupport type')
					types.append('export type %s = %s;' % (name,'{\n\t%s\n}' % ',\n\t'.join(_types)))
				else:
					_keys = []
					_types = []
					for (k,v) in table.items():
						_keys.append("'%s'" % k)
						if isinstance(v, (list, tuple)):
							_types.append(getSubType(buildList(v, 1)))
						elif isinstance(v, dict):
							_types.append(getSubType(buildDict(v, 1)))
						elif isinstance(v, str):
							_types.append('string')
						elif isinstance(v, (int, float)):
							_types.append('number')
						else:
							raise Exception('unsupport type')
					types.append('export type %s = %s;' % (name,'{\n\t[T in %s]:%s\n}' % (' | '.join(_keys) ,' | '.join(list(set(_types))))))

		# 拼装内容
		contents = []
		for (t,n) in subTypes.items():
			contents.append('type %s = %s;' % (n,t))
		contents.extend(types)
		declarations = DECLARATION_FILE % ('\n'.join(contents))

		return declarations

	def publishDeclaration(self):
		'''
			发布声明
		'''
		for target in self._publishTarget:
			if target == 'client':
				cpath = utils.fullConfigPath(self._config['path']['client'])
				if os.path.exists(cpath):
					typesFile = os.path.join(cpath, '@types', 'lcc-tables.d.ts')
					declarations = self.buildDeclaration(self._tmempClientAssetPath)
				else:
					continue
			elif target == 'server':
				spath = utils.fullConfigPath(self._config['path']['server'])
				if os.path.exists(spath):
					typesFile = os.path.join(spath, 'assets', 'lcc-assets', '@types', 'lcc-tables.d.ts')
					declarations = self.buildDeclaration(self._tmempServerAssetPath)
				else:
					continue
			else:
				continue
			fileDir = os.path.dirname(typesFile)
			if not os.path.exists(fileDir):
				os.makedirs(fileDir)
			with open(typesFile, 'w') as f:
				f.write(declarations)

		return True

	def publish(self):
		'''
			发布数据表文件
		'''
		updates = self.checkUpdate()
		if (self._publishForce or any(updates)):
			fs.clearDirectory(self._tmempClientAssetPath)
			fs.clearDirectory(self._tmempServerAssetPath)
		for f in os.listdir(C.BUILD_CACHES_PATH):
			packPath = os.path.join(C.BUILD_CACHES_PATH, f)
			if os.path.isdir(packPath) and (self._publishForce or any(updates)):
				for target in self._publishTarget:
					if target == 'client':
						if not self.publishClient(packPath):
							return False
					elif target == 'server':
						if not self.publishServerTemp(packPath):
							return False
				fileCache = FileCache(os.path.join(packPath, 'publish-tables-cache.json'))
				fileCache.load()
				fileCache.updateFiles(self.collectFiles(packPath))
				fileCache.save()
		if self._publishForce or any(updates):
			self.publishServer()
			self.publishDeclaration()
		return True

def publish():
	return TablePublisher().publish()

